#pragma once

#include <windows.h>
#include <set>

#include <boostext/format.hpp>
#include <boostext/exception.hpp>




#ifdef _DEBUG
	/**
	 * This function tests if the calling thread is actually
	 * the main thread or another.
	 * However this test is only performed in debug builds.
	 */
	void runsOnMainThread();
#else
	#define runsOnMainThread()
#endif




template
<
	class       Hook_t,
	int         HookId
>
class Hook
{
public:

	typedef Hook_t            Hook_t;
	typedef std::set<Hook_t*> Hooks_t;

protected:

	Hooks_t   m_hooks;
	DWORD     m_threadId;
	HHOOK     m_hookHandle;

public:

	Hook(HOOKPROC hookProc)
		: m_threadId(NULL)
		, m_hookHandle(NULL)
	{
		runsOnMainThread();

		m_threadId   = GetCurrentThreadId();
		m_hookHandle = SetWindowsHookEx(HookId, hookProc, NULL, m_threadId);

		if(!m_hookHandle)
			BOOSTEXT_THROW(boostext::critical_exception("Unable to install hook")
			 << boostext::win_last_err(GetLastError())
			);
	}

	virtual ~Hook()
	{
		runsOnMainThread();

		if(!UnhookWindowsHookEx(m_hookHandle))
			BOOSTEXT_THROW(boostext::critical_exception("Unable to uninstall hook")
			<< boostext::win_last_err(GetLastError())
			);
	}



	/**
	 * Get the number of installed hooks.
	 */
	std::size_t numHooks() const
	{
		return m_hooks.size();
	}



	/**
	 * Register a new Hook with the helper.
	 */
	void registerHook(Hook_t *hook)
	{
		runsOnMainThread();

		m_hooks.insert(hook);
	}

	/**
	 * Unregister a previously registered hook with the helper.
	 */
	void unregisterHook(Hook_t *hook)
	{
		runsOnMainThread();

		m_hooks.erase(hook);
	}

private:

	std::string hookName(int hookId)
	{
		switch(hookId)
		{
			case WH_CALLWNDPROC:     return "WH_CALLWNDPROC";
			case WH_CALLWNDPROCRET:  return "WH_CALLWNDPROCRET";
			case WH_CBT:             return "WH_CBT";
			case WH_DEBUG:           return "WH_DEBUG";
			case WH_FOREGROUNDIDLE:  return "WH_FOREGROUNDIDLE";
			case WH_GETMESSAGE:      return "WH_GETMESSAGE";
			case WH_JOURNALPLAYBACK: return "WH_JOURNALPLAYBACK";
			case WH_JOURNALRECORD:   return "WH_JOURNALRECORD";
			case WH_KEYBOARD:        return "WH_KEYBOARD";
			case WH_KEYBOARD_LL:     return "WH_KEYBOARD_LL";
			case WH_MOUSE:           return "WH_MOUSE";
			case WH_MOUSE_LL:        return "WH_MOUSE_LL";
			case WH_MSGFILTER:       return "WH_MSGFILTER";
			case WH_SHELL:           return "WH_SHELL";
			case WH_SYSMSGFILTER:    return "WH_SYSMSGFILTER";
			default:                 BOOSTEXT_THROW(boostext::invalid_value_exception(hookId));
		}
	}
};
///////////////////////////////////////////////////////////////////////////////////////////////////
